/*******************************************************************************
 * Copyright (c) 2009 IBM Corporation and others
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.aseclipse.osgi.classes
{
    import mx.collections.ArrayCollection;
    import mx.core.Application;
    
    import org.aseclipse.osgi.BundleEvent;
    
    public class BundleResolveService
    {
        private var resolvingQueueBusy:Boolean = false;
        private var resolvingBundleQueue:Array = new Array();
        // cache for easy accessing
        private var resolvingBundleMap:Object = new Object();
        
        private var framework:BundleFrameworkImpl;
        public function BundleResolveService(frame:BundleFrameworkImpl)
        {
            framework = frame;
            framework.getBundleDispatcher().addEventListener(BundleEvent.BUNDLECHANGED, bundleChangeHandler);
        }
        
        /****
        * Listening on bundle changes of Installed/Resolved
        * If a bundle is installed and also in resolving queue, then check it is resolveable, 
        *     if yes, then resolve it
        *     if no, check why it is not resolveable: has required bundles? they are installed/resolved?
        * If a bundle A is resolved and bundle B is in resolving queue, also B requires A, then check B is resolveable, 
        *    if yes, then resolve B. 
        */ 
        private function bundleChangeHandler(event:BundleEvent):void {
            var state:int = event.getState();
            var bundle:BundleImpl = event.getBundle() as BundleImpl;
            var id:String = bundle.getSymbolicName();
            // If a bundle is installed, and we need to check whether it is in 
            // resolving queue, if it is, then check it can be resolved.
            if(state == BundleEvent.INSTALLED) {
                trace("[BundleResolveService.bundleChangeHandler] "+id+" is installed ");
                if(containBundle(id) == true) { 
                    // // check itself is resolved
                    if(framework.isResolveable(id) == true) {
                        trace("[BundleResolveService.bundleChangeHandler] "+id+" is resolveable, resolve itself ");
                        bundle.resolve(); // 
                    } else { // handle just installed bundle with requires
                        // if bundle is installed but required bundles are not installed
                        // then also resolve these required bundles
                        var flag:Boolean = bundle.requiresResolving;
                        if(bundle.hasRequires() == true && flag == false) {
                            trace("[BundleResolveService.bundleChangeHandler] "+id+" is not resolveable, resolve its required bundle ");
                            bundle.requiresResolving = true;
                            framework.resolveRequiredBundles(id);
                        }
                    }
                }
            } 
            // If a bundle is resolved, and we need to check its dependent bundles
            // are waiting for it. If it is, 
            else if(event.getState() == BundleEvent.RESOLVED) {
                trace("[BundleResolveService.bundleChangeHandler] "+id+" is resolved ");
                if(containBundle(id) == true) {  // remove its task from queue
                    removeTask(id);
                }
                var array:Array = framework.getDependentBundles(id);
                for each(var item:String in array) {
                    if(containBundle(item) == true && framework.isResolveable(item) == true) {
                        trace("[BundleResolveService.bundleChangeHandler] "+id+"'s dependent bundle "+item+" is resolveable, resolve it ");
                        var bund:BundleImpl = framework.getBundle(item) as BundleImpl;
                        bund.resolve(); // change bundle status internally and notify others
                    }
                }
            }
        }
        
        private function containBundle(id:String):Boolean {
            return resolvingBundleMap[id] != null;
        }
        
        /********
        * Some bundle is resolved, 
        * remove it's task from queue
        */ 
        private function removeTask(id:String):void {
            trace("remove bundle "+id + " from resolving task");
            if(resolvingQueueBusy == false) {
                resolvingQueueBusy = true;
                var list:ArrayCollection = new ArrayCollection(resolvingBundleQueue);
                var resolveableList:Array = new Array();
                for(var i:int=0;i<list.length;i++) {
                    var task:Object = list.getItemAt(i);
                    var pluginId:String = task.pluginId as String;
                    if(pluginId == id) {
                        list.removeItemAt(i);
                        resolvingBundleMap[id] = null;
                        break;
                    }
                }
                resolvingQueueBusy = false;    
            } else { // wait unitl lock is release
                Application.application.callLater(removeTask, [id]);
            }
        }
        
        /***********
        * Resolve specified bundle
        * @param task map which contains pluginId.
        */ 
        public function resolve(task:Object):void {
            if(containBundle(task.pluginId)) {
                trace(task.pluginId + " is already in the resolving list");
                run();
                return;
            }
            // write lock on resolvingBundleQueue
            if(resolvingQueueBusy == false) {
                resolvingQueueBusy = true;
                resolvingBundleQueue.push(task);
                var plugin:String = task.pluginId as String;
                resolvingBundleMap[plugin] = plugin;
                resolvingQueueBusy = false;    
                run();
            } else {
                Application.application.callLater(resolve, [task]);
            }
        }
        
        // read lock on startingBundleQueue
        protected function run():void {
            if(resolvingBundleQueue.length > 0 && resolvingQueueBusy == false) {
                resolvingQueueBusy = true;
                // trigger one by one
                var list:ArrayCollection = new ArrayCollection(resolvingBundleQueue);
                for (var i:int =0 ; i<list.length; i++) {
                    var task:Object = list.getItemAt(i) as Object;
                    var id:String = task.pluginId;
                    var bundle:BundleImpl = framework.getBundle(id) as BundleImpl;
                    // doesn't install it, wait
                    if(bundle == null) {
                        trace("[run] "+id+" is not installed, ignore...");
                        // wait
                    } else { // already install, but need to resolve
                        // firstly resolve requried bundle
                        if(bundle.hasRequires() == true) {
                            if (bundle.requiresResolving == false) {
                                bundle.requiresResolving = true;
                                Application.application.callLater(framework.resolveRequiredBundles, [id]);
                                trace("[run] "+id+" resolve required bundles");
                            } else if(framework.isResolveable(id) == true) {
                                bundle.resolve();
                            }
                        } else { // no required bundle, resolve it directly 
                            bundle.resolve();
                            trace("[run] "+id+" resolve itself");
                        }
                    }
                }
                resolvingQueueBusy = false;
            } else {
                trace("[run] service is busy...");
            }
        }
                
    }
}